///////////////////////////////////////////////////////////////////////////////////////////////////
/// \file formic/fqmc/gradient.cpp
///
/// \brief   implementation file for the monte carlo gradient function
///
///////////////////////////////////////////////////////////////////////////////////////////////////

#include<vector>
#include<string>
#include<numeric>
#include<cassert>
#include<algorithm>

#include<boost/format.hpp>
#include<boost/shared_ptr.hpp>

#include<formic/fqmc/gradient_acc.h>
#include<formic/fqmc/gradient.h>
#include<formic/fqmc/sampler.h>
#include<formic/wfn/configuration.h>
#include<formic/fqmc/vmc_walker.h>
#include<formic/wfn/wfn.h>
#include<formic/input/base.h>
#include<formic/mpi/interface.h>
#include<formic/numeric/numeric.h>
#include<formic/output/output.h>
#include<formic/exception.h>
#include<formic/lapack/interface.h>
#include<formic/random/random.h>

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief  compute the energy gradient using the supplied guiding and value functions
///
/// \param[in]      userinp        the user's input options
/// \param[in]      sampler        object controlling the sampling
/// \param[in]      vf             a shared pointer to the value function
/// \param[out]     grad           size == 1 * vf->n_der() for real numbers.
///                                size == 2 * vf->n_der() for complex numbers.
///                                on exit, the energy gradient in real number format
///
///////////////////////////////////////////////////////////////////////////////////////////////////
template<class S> void formic::fqmc::gradient(const formic::InputBase & userinp,
                                              boost::shared_ptr<formic::fqmc::Sampler<S> > & sampler,
                                              const boost::shared_ptr<formic::CoeffObj<S> > & vf,
                                              double * const grad,
                                              const double * const energy_ptr) {

  // create an accumulator for the gradient
  boost::shared_ptr<formic::Accumulator<S> > gacc( new formic::fqmc::GradAcc<S>(sampler->gf(), vf, true, energy_ptr) );

  // sample the gradient
  sampler->simple_sample() << vf << gacc;

  // get a derived class pointer to the gradient accumulator
  formic::fqmc::GradAcc<S> * gacc_ptr = dynamic_cast<formic::fqmc::GradAcc<S> *>(gacc.get());

  // print statistics of gradient sampling
  gacc_ptr->print_statistics(formic::of);

  // get the gradient
  gacc_ptr->get_real_gradient(grad);

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief  compute the energy gradient using the supplied guiding and value functions
///
/// \param[in]      userinp        the user's input options
/// \param[in]      burn_len       the burn-in length
/// \param[in]      samp_len       the sample length
/// \param[in]      init_config    the initial configuration
/// \param[in]      rng            the random number generator to use
/// \param[in]      gf             a shared pointer to the guiding function
/// \param[in]      vf             a shared pointer to the value function
/// \param[out]     grad           size == 1 * vf->n_der() for real numbers.
///                                size == 2 * vf->n_der() for complex numbers.
///                                on exit, the energy gradient in real number format
///
///////////////////////////////////////////////////////////////////////////////////////////////////
template<class S> void formic::fqmc::gradient(const formic::InputBase & userinp,
                                              const int burn_len,
                                              const int samp_len,
                                              const formic::Configuration & init_config,
                                              const formic::LaggedFibonacci<> & rng,
                                              const boost::shared_ptr<formic::CoeffObj<S> > & gf,
                                              const boost::shared_ptr<formic::CoeffObj<S> > & vf,
                                              double * const grad,
                                              const double * const energy_ptr) {

  // construct an object to control the sampling
  boost::shared_ptr<formic::fqmc::Sampler<S> > sampler( new formic::fqmc::Sampler<S>(userinp, burn_len, samp_len, init_config, rng, gf) );

  // get the gradient
  formic::fqmc::gradient(userinp, sampler, vf, grad, energy_ptr);

}

///////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief  compute the energy gradient of the supplied coefficient object
///
/// \param[in]      userinp        the user's input options
/// \param[in]      co             a shared pointer to the coefficient object
/// \param[out]     grad           size == 1 * vf->n_der() for real numbers.
///                                size == 2 * vf->n_der() for complex numbers.
///                                on exit, the energy gradient in real number format
///
///////////////////////////////////////////////////////////////////////////////////////////////////
template<class S> void formic::fqmc::gradient(const formic::InputBase & userinp,
                                              const boost::shared_ptr<formic::CoeffObj<S> > & co,
                                              double * const grad,
                                              const double * const energy_ptr) {

  // get initial configuration
  formic::Configuration init_config(userinp);

  // get burn in and sample lengths
  const int burn_len = userinp.get<int>("gradient_burn_len");
  const int samp_len = userinp.get<int>("gradient_samp_len");

  // compute the gradient
  formic::fqmc::gradient(userinp, burn_len, samp_len, init_config, formic::LaggedFibonacci<>(), co, co, grad, energy_ptr);

}

template void formic::fqmc::gradient(const formic::InputBase & userinp, const boost::shared_ptr<formic::CoeffObj<double> > & co, double * const, const double * const);
template void formic::fqmc::gradient(const formic::InputBase & userinp, const boost::shared_ptr<formic::CoeffObj<std::complex<double> > > & co, double * const, const double * const);
